﻿using System;
using System.Text.RegularExpressions;

namespace AssemblerCompiler
{
    internal class Operand
    {
        public enum AddressingType
        {
            Register,
            Immediate,
            Pointer,
            RegisterImplicit,
            Based,
            PointerWithIndexing
        }

        [Flags]
        public enum Register
        {
            AL = (1 << 0),
            AX = (1 << 1),
            CL = (1 << 2),
            CX = (1 << 3),
            DL = (1 << 4),
            DX = (1 << 5),
            BL = (1 << 6),
            BX = (1 << 7),
            AH = (1 << 8),
            SP = (1 << 9),
            CH = (1 << 10),
            BP = (1 << 11),
            DH = (1 << 12),
            SI = (1 << 13),
            BH = (1 << 14),
            DI = (1 << 15),
            ES = (1 << 16),
            CS = (1 << 17),
            SS = (1 << 18),
            DS = (1 << 19),
            FS = (1 << 20),
            GS = (1 << 21)
        }

        public enum Size
        {
            Byte,
            Word,
            Undefined
        }

        private readonly AddressingType at;
        private readonly UInt16 im;
        private readonly Register ind;
        private readonly bool isInd;
        private readonly UInt16 offset;
        private readonly Register r;
        private readonly Size size;
        private readonly string variableName;
        private bool isOffset;

        public Operand(string op)
        {
            var registerRegex =
                new Regex(@"^\s*(?<reg>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*$",
                          RegexOptions.IgnoreCase);
            var immediateRegex =
                new Regex(
                    @"^\s*(?<size>(BYTE|WORD))?\s*((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)\s*$",
                    RegexOptions.IgnoreCase);
            var pointerRegex = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s*$", RegexOptions.IgnoreCase);
            var registerImplicitRegex =
                new Regex(
                    @"^\s*\[\s*(?<reg>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*\]\s*(\[\s*(?<ind>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*\])?\s*$",
                    RegexOptions.IgnoreCase);
            var basedRegex =
                new Regex(
                    @"^\s*\[\s*(?<reg>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*\]\s*(\[\s*(?<ind>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*\])?\s*\+\s*((?<hex>[0-9][0-9A-F]*)H|(?<dec>[0-9]+)|(?<dec1>[0-9]+)D|(?<bin>[0-1]+)B)\s*$",
                    RegexOptions.IgnoreCase);
            var pointerWithIndexingRegex =
                new Regex(
                    @"(^\s*(?<name>[_a-zA-Z][_a-zA-Z0-9]*)\s*\[\s*(?<reg>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*\]\s*$|^\s*(?<reg>AL|AX|CL|CX|DL|DX|BL|BX|AH|SP|CH|BP|DH|SI|BH|DI|ES|CS|SS|DS|FS|GS)\s*\+(?<name>[_A-Z][_A-Z0-9]*)\s*$)",
                    RegexOptions.IgnoreCase);

            Match m = registerRegex.Match(op);
            if (m.Success)
            {
                this.at = AddressingType.Register;
                this.r = GetRegister(m.Groups["reg"].Value);
                return;
            }

            m = immediateRegex.Match(op);
            if (m.Success)
            {
                this.at = AddressingType.Immediate;
                if (m.Groups["hex"].Value != String.Empty)
                {
                    this.im = ConverterClass.HexToUInt16(m.Groups["hex"].Value);
                }
                if (m.Groups["dec"].Value != String.Empty)
                {
                    this.im = ConverterClass.DecToUInt16(m.Groups["dec"].Value);
                }
                if (m.Groups["dec1"].Value != String.Empty)
                {
                    this.im = ConverterClass.DecToUInt16(m.Groups["dec1"].Value);
                }
                if (m.Groups["bin"].Value != String.Empty)
                {
                    this.im = ConverterClass.BinToUInt16(m.Groups["bin"].Value);
                }
                if (m.Groups["size"].Value != String.Empty)
                {
                    this.size = m.Groups["size"].Value.ToUpper() == "BYTE" ? Size.Byte : Size.Word;
                }
                else
                {
                    this.size = Size.Undefined;
                }
                return;
            }

            m = pointerRegex.Match(op);
            if (m.Success)
            {
                this.at = AddressingType.Pointer;
                this.variableName = m.Groups["name"].Value.ToUpper();
                return;
            }

            m = registerImplicitRegex.Match(op);
            if (m.Success)
            {
                this.at = AddressingType.RegisterImplicit;
                this.r = GetRegister(m.Groups["reg"].Value);
                if (m.Groups["ind"].Value != String.Empty)
                {
                    this.isInd = true;
                    this.ind = GetRegister(m.Groups["ind"].Value);
                }
                else
                {
                    this.isInd = false;
                }
                return;
            }

            m = basedRegex.Match(op);
            if (m.Success)
            {
                this.at = AddressingType.Based;
                this.r = GetRegister(m.Groups["reg"].Value);
                if (m.Groups["ind"].Value != String.Empty)
                {
                    this.isInd = true;
                    this.ind = GetRegister(m.Groups["ind"].Value);
                }
                else
                {
                    this.isInd = false;
                }
                if (m.Groups["hex"].Value != String.Empty)
                {
                    this.offset = ConverterClass.HexToUInt16(m.Groups["hex"].Value);
                }
                if (m.Groups["dec"].Value != String.Empty)
                {
                    this.offset = ConverterClass.DecToUInt16(m.Groups["dec"].Value);
                }
                if (m.Groups["dec1"].Value != String.Empty)
                {
                    this.offset = ConverterClass.DecToUInt16(m.Groups["dec1"].Value);
                }
                if (m.Groups["bin"].Value != String.Empty)
                {
                    this.offset = ConverterClass.BinToUInt16(m.Groups["bin"].Value);
                }
                return;
            }

            m = pointerWithIndexingRegex.Match(op);
            if (m.Success)
            {
                this.at = AddressingType.PointerWithIndexing;
                this.variableName = m.Groups["name"].Value.ToUpper();
                this.r = GetRegister(m.Groups["reg"].Value);
                return;
            }

            throw new Exception("Unknown operand type!");
        }

        public AddressingType AT
        {
            get
            {
                return this.at;
            }
        }

        public Register R
        {
            get
            {
                return this.r;
            }
        }

        public UInt16 IM
        {
            get
            {
                return this.im;
            }
        }

        public string VariableName
        {
            get
            {
                return this.variableName;
            }
        }

        public UInt16 Offset
        {
            get
            {
                return this.offset;
            }
        }

        public Size Size_
        {
            get
            {
                return this.size;
            }
        }

        public SymTabElement.SymType SymType_ { get; set; }

        public bool IsInd
        {
            get
            {
                return this.isInd;
            }
        }

        public Register Ind
        {
            get
            {
                return this.ind;
            }
        }

        public static Size getSize(Register r)
        {
            if (r == Register.AL || r == Register.AH)
            {
                return Size.Byte;
            }
            if (r == Register.BL || r == Register.BH)
            {
                return Size.Byte;
            }
            if (r == Register.CL || r == Register.CH)
            {
                return Size.Byte;
            }
            if (r == Register.DL || r == Register.DH)
            {
                return Size.Byte;
            }
            return Size.Word;
        }

        public static Byte getRegBits(Register r)
        {
            if (r == Register.AL || r == Register.AX)
            {
                return 0;
            }
            if (r == Register.CL || r == Register.CX)
            {
                return 1;
            }
            if (r == Register.DL || r == Register.DX)
            {
                return 2;
            }
            if (r == Register.BL || r == Register.BX)
            {
                return 3;
            }
            if (r == Register.AH || r == Register.SP)
            {
                return 4;
            }
            if (r == Register.CH || r == Register.BP)
            {
                return 5;
            }
            if (r == Register.DH || r == Register.SI)
            {
                return 6;
            }
            if (r == Register.BH || r == Register.DI)
            {
                return 7;
            }

            throw new Exception("Internal complier error in getting register bits!");
        }

        public static Byte getSregBits(Register r)
        {
            if (r == Register.ES)
            {
                return 0;
            }
            if (r == Register.CS)
            {
                return 1;
            }
            if (r == Register.SS)
            {
                return 2;
            }
            if (r == Register.DS)
            {
                return 3;
            }
            if (r == Register.FS)
            {
                return 4;
            }
            if (r == Register.GS)
            {
                return 5;
            }

            throw new Exception("Internal complier error in getting register bits!");
        }

        public static Register GetRegister(string reg)
        {
            switch (reg.ToUpper())
            {
                case "AL":
                    return Register.AL;
                case "AX":
                    return Register.AX;
                case "CL":
                    return Register.CL;
                case "CX":
                    return Register.CX;
                case "DL":
                    return Register.DL;
                case "DX":
                    return Register.DX;
                case "BL":
                    return Register.BL;
                case "BX":
                    return Register.BX;
                case "AH":
                    return Register.AH;
                case "SP":
                    return Register.SP;
                case "CH":
                    return Register.CH;
                case "BP":
                    return Register.BP;
                case "DH":
                    return Register.DH;
                case "SI":
                    return Register.SI;
                case "BH":
                    return Register.BH;
                case "DI":
                    return Register.DI;
                case "ES":
                    return Register.ES;
                case "CS":
                    return Register.CS;
                case "SS":
                    return Register.SS;
                case "DS":
                    return Register.DS;
                case "FS":
                    return Register.FS;
                case "GS":
                    return Register.GS;
            }

            throw new Exception("Internal compiler error in getting register from string!");
        }

        public static Byte GetModRMByte(Operand op)
        {
            if (op.AT == AddressingType.Immediate)
            {
                throw new Exception("Internal compiler error in gettin ModRM Byte!");
            }

            if (op.AT == AddressingType.Register)
            {
                return (Byte) ((3 << 6) | getRegBits(op.R));
            }

            if (op.AT == AddressingType.Pointer)
            {
                return 6;
            }

            if (op.AT == AddressingType.RegisterImplicit)
            {
                if (op.IsInd)
                {
                    if (op.R == Register.BX && op.Ind == Register.SI)
                    {
                        return 0;
                    }
                    if (op.R == Register.BX && op.Ind == Register.DI)
                    {
                        return 1;
                    }
                    if (op.R == Register.BP && op.Ind == Register.SI)
                    {
                        return 2;
                    }
                    if (op.R == Register.BP && op.Ind == Register.DI)
                    {
                        return 3;
                    }
                }
                else
                {
                    if (op.R == Register.SI)
                    {
                        return 4;
                    }
                    if (op.R == Register.DI)
                    {
                        return 5;
                    }
                    if (op.R == Register.BX)
                    {
                        return 7;
                    }
                }
                throw new Exception("Invalid register in operand!");
            }

            if (op.AT == AddressingType.Based)
            {
                if (op.IsInd)
                {
                    if (op.R == Register.BX && op.Ind == Register.SI)
                    {
                        return 128 | 0;
                    }
                    if (op.R == Register.BX && op.Ind == Register.DI)
                    {
                        return 128 | 1;
                    }
                    if (op.R == Register.BP && op.Ind == Register.SI)
                    {
                        return 128 | 2;
                    }
                    if (op.R == Register.BP && op.Ind == Register.DI)
                    {
                        return 128 | 3;
                    }
                }
                else
                {
                    if (op.R == Register.SI)
                    {
                        return 128 | 4;
                    }
                    if (op.R == Register.DI)
                    {
                        return 128 | 5;
                    }
                    if (op.R == Register.BP)
                    {
                        return 128 | 6;
                    }
                    if (op.R == Register.BX)
                    {
                        return 128 | 7;
                    }
                }
                throw new Exception("Invalid register in operand!");
            }

            if (op.AT == AddressingType.PointerWithIndexing)
            {
                if (op.R == Register.SI)
                {
                    return 128 | 4;
                }
                if (op.R == Register.DI)
                {
                    return 128 | 5;
                }
                if (op.R == Register.BP)
                {
                    return 128 | 6;
                }
                if (op.R == Register.BX)
                {
                    return 128 | 7;
                }

                throw new Exception("Invalid register in operand!");
            }

            throw new Exception("Internal compiler error!");
        }
    }
}